import { describe, expect, it } from "vitest";
import { Vector } from "../../../src/common/vector";
import { Dom } from "../../../src/common/dom";

const wrap = document.createElement("div");
const svgContent =
  '<path id="svg-path" d="M10 10"/>' +
  "<!-- comment -->" +
  '<g id="svg-group">' +
  '  <ellipse id="svg-ellipse" x="10" y="10" rx="30" ry="30"/>' +
  '  <circle id="svg-circle" cx="10" cy="10" r="2" fill="red"/>' +
  "</g>" +
  '<polygon id="svg-polygon" points="200,10 250,190 160,210"/>' +
  '<text id="svg-text" x="0" y="15" fill="red">Test</text>' +
  '<rect id="svg-rectangle" x="100" y="100" width="50" height="100"/>' +
  '<g id="svg-group-1" class="group-1">' +
  '  <g id="svg-group-2" class="group-2">' +
  '    <g id="svg-group-3" class="group3">' +
  '      <path id="svg-path-2" d="M 100 100 C 100 100 0 150 100 200 Z"/>' +
  "    </g>" +
  "  </g>" +
  "</g>" +
  '<path id="svg-path-3"/>' +
  '<linearGradient id= "svg-linear-gradient"><stop/></linearGradient>' +
  '<foreignObject x="20" y="20" width="160" height="160">' +
  '<body xmlns="http://www.w3.org/1999/xhtml">' +
  '<div id="foreign-div"></div>' +
  "</body>" +
  "</foreignObject>";

Vector.create(
  "svg",
  { id: "svg-container" },
  Vector.createVectors(svgContent),
).appendTo(wrap);

export function setupTest() {
  document.body.appendChild(wrap);
  const byId = <T>(id: string) => document.getElementById(id) as any as T;
  return {
    wrap,
    svgContainer: byId<SVGSVGElement>("svg-container"),
    svgDefs: byId<SVGPathElement>("svg-defs"),
    svgPath: byId<SVGPathElement>("svg-path"),
    svgGroup: byId<SVGGElement>("svg-group"),
    svgCircle: byId<SVGCircleElement>("svg-circle"),
    svgEllipse: byId<SVGEllipseElement>("svg-ellipse"),
    svgPolygon: byId<SVGPolygonElement>("svg-polygon"),
    svgText: byId<SVGTextElement>("svg-text"),
    svgRectangle: byId<SVGRectElement>("svg-rectangle"),
    svgGroup1: byId<SVGGElement>("svg-group-1"),
    svgGroup2: byId<SVGGElement>("svg-group-2"),
    svgGroup3: byId<SVGGElement>("svg-group-3"),
    svgPath2: byId<SVGPathElement>("svg-path-2"),
    svgPath3: byId<SVGPathElement>("svg-path-3"),
    svgLinearGradient: byId<SVGLinearGradientElement>("svg-linear-gradient"),
    foreignDiv: byId<HTMLElement>("foreign-div"),
  };
}

export function clearnTest() {
  Dom.remove(wrap);
}

describe("Dom", () => {
  describe("elem", () => {
    const childrenTagNames = (vel: Vector) => {
      const tagNames: string[] = [];
      vel.node.childNodes.forEach((childNode) => {
        tagNames.push((childNode as HTMLElement).tagName.toLowerCase());
      });
      return tagNames;
    };

    const {
      svgContainer,
      svgPath,
      svgGroup,
      svgCircle,
      svgEllipse,
      svgPolygon,
      svgText,
      svgRectangle,
      svgGroup1,
      svgGroup2,
      svgGroup3,
      svgPath2,
      svgPath3,
      svgLinearGradient,
    } = setupTest();

    afterAll(() => clearnTest());

    describe("#ensureId", () => {
      it("should set a id when id is empty", () => {
        const node = document.createElement("g") as any as SVGElement;
        expect(node.id).toBe("");

        const id = Dom.ensureId(node);
        expect(node.id).toEqual(id);
      });

      it("should not overwrite if id exited", () => {
        const node = document.createElement("g") as any as SVGElement;
        expect(node.id).toBe("");

        const id = Dom.ensureId(node);
        expect(node.id).toEqual(id);
        expect(Dom.ensureId(node)).toEqual(id);
      });
    });

    describe("id", () => {
      it("should auto generate id", () => {
        const vel = Vector.create("rect");
        expect(vel.id).not.toBeNull();
        expect(vel.id).toEqual(vel.node.id);
      });

      it("should set id for node", () => {
        const vel = Vector.create("rect");
        vel.id = "xxx";
        expect(vel.id).toEqual("xxx");
        expect(vel.id).toEqual(vel.node.id);
      });
    });

    describe("#isSVGGraphicsElement", () => {
      it("should return true when the given element is a SVGGraphicsElement", () => {
        expect(Dom.isSVGGraphicsElement(Vector.create("circle").node)).toBe(
          true,
        );
        expect(Dom.isSVGGraphicsElement(Vector.create("rect").node)).toBe(true);
        expect(Dom.isSVGGraphicsElement(Vector.create("path").node)).toBe(true);
      });

      it.skip("should return false when the given element is not a SVGGraphicsElement", () => {
        expect(Dom.isSVGGraphicsElement()).toBe(false);
        expect(
          Dom.isSVGGraphicsElement(Vector.create("linearGradient").node),
        ).toBe(false);
      });
    });

    describe("#index", () => {
      it("should return 0 for the first child", () => {
        expect(Vector.create(svgContainer).index()).toEqual(0);
        expect(Vector.create(svgPath).index()).toEqual(0);
      });

      it("should return correct index of children", () => {
        expect(Vector.create(svgPath).index()).toEqual(0);
        expect(Vector.create(svgGroup).index()).toEqual(1);
        expect(Vector.create(svgPolygon).index()).toEqual(2);
        expect(Vector.create(svgText).index()).toEqual(3);
        expect(Vector.create(svgRectangle).index()).toEqual(4);

        expect(Vector.create(svgEllipse).index()).toEqual(0);
        expect(Vector.create(svgCircle).index()).toEqual(1);
      });
    });

    describe("#tagName", () => {
      it("should return the correct tagName with lowercase", () => {
        expect(Vector.create(svgContainer).tagName()).toEqual("svg");
        expect(Vector.create(svgPath).tagName()).toEqual("path");
        expect(Vector.create(svgGroup).tagName()).toEqual("g");
        expect(Vector.create(svgCircle).tagName()).toEqual("circle");
        expect(Vector.create(svgEllipse).tagName()).toEqual("ellipse");
        expect(Vector.create(svgPolygon).tagName()).toEqual("polygon");
        expect(Vector.create(svgText).tagName()).toEqual("text");
        expect(Vector.create(svgRectangle).tagName()).toEqual("rect");
        expect(Vector.create(svgGroup1).tagName()).toEqual("g");
        expect(Vector.create(svgGroup2).tagName()).toEqual("g");
        expect(Vector.create(svgGroup3).tagName()).toEqual("g");
        expect(Vector.create(svgPath2).tagName()).toEqual("path");
        expect(Vector.create(svgPath3).tagName()).toEqual("path");
        expect(Vector.create(svgLinearGradient).tagName()).toEqual(
          "lineargradient",
        );
      });

      it("should return uppercase tagName when specified", () => {
        expect(Dom.tagName(svgContainer, false)).toEqual("SVG");
      });
    });

    describe("#find", () => {
      it("should return an array of vectors", () => {
        const container = Vector.create(svgContainer);
        const found = container.find("circle");
        expect(found).toBeInstanceOf(Array);
        expect(found.length > 0).toBeTruthy();
        expect(found.every((f) => f instanceof Vector)).toBe(true);
      });
    });

    describe("#findOne", () => {
      it("should return the first found vector", () => {
        const container = Vector.create(svgContainer);
        const found = container.findOne("circle");
        expect(found).toBeInstanceOf(Vector);
        expect(found!.id).toEqual("svg-circle");
      });
    });

    describe("#findParentByClass", () => {
      it("should return parent vector if exists", () => {
        const found = Vector.create(svgGroup3).findParentByClass("group-1");
        expect(found != null && found.node === svgGroup1).toBe(true);
      });

      it("should return null if none parent matched", () => {
        const found =
          Vector.create(svgGroup3).findParentByClass("not-a-parent");
        expect(found == null).toBe(true);
      });

      it("should stopped early", () => {
        const found1 = Vector.create(svgGroup3).findParentByClass(
          "group-1",
          svgGroup2,
        );
        expect(found1 == null).toBe(true);

        const found2 = Vector.create(svgGroup3).findParentByClass(
          "group-1",
          svgCircle,
        );
        expect(found2 != null && found2.node === svgGroup1).toBe(true);
      });
    });

    describe("#contains", () => {
      it("...", () => {
        expect(Vector.create(svgContainer).contains(svgGroup1)).toBe(true);
        expect(Vector.create(svgGroup1).contains(svgGroup2)).toBe(true);
        expect(Vector.create(svgGroup1).contains(svgGroup3)).toBe(true);

        expect(Vector.create(svgGroup3).contains(svgGroup1)).toBe(false);
        expect(Vector.create(svgGroup2).contains(svgGroup1)).toBe(false);
        expect(Vector.create(svgGroup1).contains(svgGroup1)).toBe(false);
        expect(Vector.create(svgGroup1).contains(document as any)).toBe(false);
      });
    });

    describe("#empty", () => {
      const vel = Vector.create("g");
      beforeEach(() => Vector.create(svgContainer).append(vel));
      afterEach(() => vel.remove());

      it("should remove all child nodes", () => {
        vel.append([
          Vector.create("rect"),
          Vector.create("polygon"),
          Vector.create("circle"),
        ]);
        expect(vel.node.childNodes.length).toEqual(3);
        vel.empty();
        expect(vel.node.childNodes.length).toEqual(0);
      });
    });

    describe("#append", () => {
      const group = Vector.create(Dom.createElement("g") as any);

      beforeEach(() => group.empty());

      it("should append single element", () => {
        group.append(Vector.create("<rect/>"));
        expect(group.node.childNodes.length).toEqual(1);
        expect(childrenTagNames(group)).toEqual(["rect"]);
      });

      it("should append multiple elements", () => {
        group.append(Vector.createVectors("<rect/><circle/>"));
        expect(group.node.childNodes.length).toEqual(2);
        expect(childrenTagNames(group)).toEqual(["rect", "circle"]);

        group.append(Vector.createVectors("<line/><polygon/>"));
        expect(group.node.childNodes.length).toEqual(4);
        expect(childrenTagNames(group)).toEqual([
          "rect",
          "circle",
          "line",
          "polygon",
        ]);
      });
    });

    describe("#prepend", () => {
      let group: Vector;

      beforeEach(() => {
        group = Vector.create(svgGroup).clone().empty().appendTo(svgContainer);
      });

      afterAll(() => group.remove());

      it("should prepend single element", () => {
        group.prepend(Vector.create("<rect/>"));
        expect(group.node.childNodes.length).toEqual(1);
        expect(childrenTagNames(group)).toEqual(["rect"]);
      });

      it("should prepend multiple elements", () => {
        group.prepend(Vector.createVectors("<rect/><circle/>"));
        expect(group.node.childNodes.length).toEqual(2);
        expect(childrenTagNames(group)).toEqual(["rect", "circle"]);

        group.prepend(Vector.createVectors("<line/><polygon/>"));
        expect(group.node.childNodes.length).toEqual(4);
        expect(childrenTagNames(group)).toEqual([
          "line",
          "polygon",
          "rect",
          "circle",
        ]);
      });
    });

    describe("#before", () => {
      let group: Vector;
      let rect: Vector;

      beforeEach(() => {
        group = Vector.create(svgGroup).clone().empty();
        rect = Vector.create(svgRectangle).clone().empty();
        group.append(rect);
      });

      afterAll(() => group.remove());

      it("should add single element", () => {
        rect.before(Vector.create("<circle/>"));
        expect(group.node.childNodes.length).toEqual(2);
        expect(childrenTagNames(group)).toEqual(["circle", "rect"]);

        rect.before(Vector.create("<line/>"));
        expect(group.node.childNodes.length).toEqual(3);
        expect(childrenTagNames(group)).toEqual(["circle", "line", "rect"]);
      });

      it("should add multiple elements", () => {
        rect.before(Vector.createVectors("<ellipse/><circle/>"));
        expect(group.node.childNodes.length).toEqual(3);
        expect(childrenTagNames(group)).toEqual(["ellipse", "circle", "rect"]);

        rect.before(Vector.createVectors("<line/><polygon/>"));
        expect(group.node.childNodes.length).toEqual(5);
        expect(childrenTagNames(group)).toEqual([
          "ellipse",
          "circle",
          "line",
          "polygon",
          "rect",
        ]);
      });
    });

    describe("#children", () => {
      it("should return a array for vectors", () => {
        const children = Vector.create(svgGroup).children();
        expect(children).toBeInstanceOf(Array);
        expect(children.every((c) => c instanceof Vector)).toEqual(true);
      });
    });
  });
});
